/** \file gu_stream_map.cpp */

#include "gu_stream_map.h"

#include "gu_shader.h"
#include "gu_vertex_buffer.h"

#include "gu_util.h"

#include <GL/glew.h>

#include<unordered_map>
#include<cassert>


namespace gu {


std::unordered_map<gr::GeometryIndexLayout, GLenum> layoutMap()
{
    std::unordered_map<gr::GeometryIndexLayout, GLenum> map;

    map[gr::TRIANGLES]  = GL_TRIANGLES; 
    map[gr::LINES]      = GL_LINES;
    map[gr::LINE_STRIP] = GL_LINE_STRIP;
    map[gr::LINE_LOOP]  = GL_LINE_LOOP; 

    return map;
}

/*Functions*/
GLenum layoutToGlLayout(gr::GeometryIndexLayout layout)
{
    GLenum result = 0;
    switch(layout)
    {
        case gr::TRIANGLES: result = GL_TRIANGLES; break;
        case gr::LINES: result = GL_LINES; break;
        case gr::LINE_STRIP: result = GL_LINE_STRIP; break;
        case gr::LINE_LOOP: result = GL_LINE_LOOP; break;
        default:assert(!"Faulty mapping!");break;
    }
    return result;
}

enum BufName{
    VERTEX_BUFFER = 0, 
    INDEX_BUFFER = 1
};

class StreamMapGLSL330 : public StreamMap
{
public:
    StreamMapGLSL330()
    {
        lastBound_[VERTEX_BUFFER] = lastBound_[INDEX_BUFFER] = 0;
    }

    void vertexBuffer(VertexBuffer* buffer) override
    {
        if(buffer != vertexBuffer_) lastBound_[VERTEX_BUFFER] = 0;
        vertexBuffer_ = buffer;
    }

    VertexBuffer* vertexBuffer() override
    {
        return vertexBuffer_;
    }

    void indexBuffer(IndexBuffer* buffer) override
    {
        if(buffer != indexBuffer_) lastBound_[INDEX_BUFFER] = 0;
        indexBuffer_ = buffer;
    }

    IndexBuffer* indexBuffer() override
    {
        return indexBuffer_;
    }

    bool attached() override
    {
        return true;
    }

    bool render() override
    {
        bool result = true;

        if(indexBuffer_ && vertexBuffer_)
        {
            auto layout = layoutToGlLayout(indexBuffer_->indexData().layout());
            auto indexCount = indexBuffer_->indexCount();

            // Check if bound. For example, instantiated objects can refer to same buffers and only update object to world matrices, for example.
            if(vertexBuffer_->id() != lastBound_[VERTEX_BUFFER])
            {
                result = vertexBuffer_->use(); // TODO lift use code here. vertexBuffer_ -> StreamData, contains only data, no operation
                lastBound_[VERTEX_BUFFER] = vertexBuffer_->id();
            }

            if(indexBuffer_->id() != lastBound_[INDEX_BUFFER] && result)
            {
                result =  indexBuffer_->use(); // TODO lift use code here. IndexBuffer -> StreamDataIndices, contains only data, no operation
                lastBound_[INDEX_BUFFER] = indexBuffer_->id();
            }

            if(result)
            {
                glDrawElements(layout, indexCount, GL_UNSIGNED_SHORT, 0);
                guCheckGlError("drawBuffer");
            }
        }
        else
        {
            assert(!"All buffers are not bound to stream map.");
            result = false;
        }
        return result;
    }

private:
    uint32_t lastBound_[2];
    VertexBuffer* vertexBuffer_;
    IndexBuffer* indexBuffer_;
};


StreamMap* newStreamMap(ShaderDialect dialect)
{
    switch(dialect)
    {
        case GLSL_330: return new StreamMapGLSL330(); break;
        default:assert(!"Unhandled enum value"); 
    }
    return nullptr;
}


}
